#ifndef CRUDE_OBSERVABLE_H_
#define CRUDE_OBSERVABLE_H_

#include <atomic>
#include <cassert>
#include <memory>
#include <set>
#include <shared_mutex>
#include <utility>

#ifndef _STD
#define _STD std::
#endif

namespace crude {
template <class T> class IObservable {
  IObservable(const IObservable &that) = delete;
  IObservable &operator=(const IObservable &that) = delete;

public:
  class Observer {
  public:
    Observer() : container_(nullptr) {}
    explicit Observer(T *pIObservable) : container_(pIObservable) {
      if (container_)
        container_->AddObserver(this);
    }
    Observer(const Observer &that) : Observer(that.Get()) {}

    ~Observer() {
      if (container_)
        container_->RemoveObserver(this);
    }

    void Reset(T *pIObservable = nullptr) {
      if (pIObservable == container_)
        return;
      if (container_)
        container_->RemoveObserver(this);
      container_ = pIObservable;
      if (container_)
        container_->AddObserver(this);
    }

    void OnDestroy() { container_ = nullptr; }

    Observer &operator=(const Observer &that) {
      Reset(that.Get());
      return *this;
    }

    bool operator==(const Observer &that) const {
      return container_ == that.container_;
    }

    bool operator<(const Observer &that) const {
      return container_ < that.container_;
    }

    bool operator!=(const Observer &that) const { return !(*this == that); }

    operator bool() const { return !!container_; }
    T *Get() const { return container_; }
    T &operator*() const { return *container_; }
    T *operator->() const { return container_; }

  private:
    T *container_;
  };

  IObservable() {}

  size_t GetObserversCount() const {
    _STD shared_lock<_STD shared_mutex> lock(mutex_);
    return observers_.size();
  }

protected:
  ~IObservable() { NotifyObservers(); }

private:
  void AddObserver(Observer *pObserver) {
    _STD unique_lock<_STD shared_mutex> lock(mutex_);
    observers_.insert(pObserver);
  }
  void RemoveObserver(Observer *pObserver) {
    _STD unique_lock<_STD shared_mutex> lock(mutex_);
    observers_.erase(pObserver);
  }
  void NotifyObservers() {
    _STD unique_lock<_STD shared_mutex> lock(mutex_);
    for (auto it = observers_.begin(); it != observers_.end(); it++) {
      auto *pObserver = *it;
      pObserver->OnDestroy();
    }
    observers_.clear();
  }

  _STD set<Observer *> observers_;
  mutable _STD shared_mutex mutex_;
};
} // namespace crude
#endif // CRUDE_OBSERVABLE_H_
